﻿using System;
using System.Collections.Generic;

namespace V2
{
    /// <summary> A stateful, efficient Cartesian Product on any number of <see cref="IStreamingSet"/>s.</summary>
    public class SetProduct : IStreamingSet
    {
        private readonly AlignedMembers[] m_MembersByInput;
        private readonly Set m_ResultSet;

        private readonly bool[] m_HadValue;

        public SetProduct(params IStreamingSet[] input_sets) : this((IList<IStreamingSet>)input_sets) { }
        public SetProduct(IList<IStreamingSet> input_sets)
        {
            m_ResultSet = new Set();
            m_MembersByInput = new AlignedMembers[input_sets.Count];
            m_HadValue = new bool[input_sets.Count];

            for (int i = 0; i < input_sets.Count; i++){
                m_MembersByInput[i] = new AlignedMembers(input_sets[i], i, input_sets.Count );
            }
            for (int i = 0; i < input_sets.Count; i++) {
                int input_set_index = i;
                m_MembersByInput[i].InputStreamingSet.RowBinding +=
                    delegate(IStreamingSet sender, ChangeArgs args) {
                        switch (args.ChangeType) {
                            case ChangeType.Add: HandleAddition(input_set_index, sender, args); break;
                            case ChangeType.Remove: HandleRemoval(input_set_index, sender, args); break;
                            case ChangeType.Update: HandleUpdate(input_set_index, sender, args); break;
                            case ChangeType.Clear: HandleClear(input_set_index, sender, args); break;
                            default: throw new ArgumentOutOfRangeException();
                        }
                    };
            }
        }
        

        private CombinedValues[] GetAssociatedCombinations(int sender_index, ChangeArgs args)
        {
            List<Values>[] source_sets = new List<Values>[m_MembersByInput.Length];

            // Sets the total number of items we expect from this change.
            // We replace source_sets with ChangeArgs's addition list, this is a critical point in how we process.
            int total_items = 1;
            for( int current_x = 0 ; current_x < m_MembersByInput.Length ; current_x++ ) {
                if( sender_index == current_x ) {
                    source_sets[ current_x ] = new List< Values >( args.AdditionItems );
                } else {
                    source_sets[ current_x ] = m_MembersByInput[ current_x ].RawInputValues;
                }
                // to change behavior to an inner join, allow multiply by zero.
                total_items *= (source_sets[current_x].Count == 0) ? 1 : source_sets[current_x].Count;
            }

            int[] indices = new int[source_sets.Length]; // keeps track of the current position being processed.
            CombinedValues[] associated_valueses = new CombinedValues[total_items];

            int current_y_index = 0; // current position
            int target_x_index = 0; // target_index is the leftmost non empty index
            for (; target_x_index < source_sets.Length; target_x_index++){
                if (source_sets[target_x_index].Count != 0) break;
            }

            // Works as follows in example source_sets[0].Count = 2 and source_sets[1].Count > 1:
            //  new Values( [0][0][0] ) <-- starting position
            //  new Values( [1][0][0] ) <-- next position
            //  new Values( [0][1][0] ) <-- clear previous, then increment next index and so on...
            while( current_y_index < total_items ) {
                for( int inner_x = 0 ; inner_x < indices.Length ; inner_x++ ) {
                    
                    if( inner_x == target_x_index ) {
                        Values[] values_parts = new Values[source_sets.Length];

                        for( int col_index = target_x_index ; col_index < indices.Length ; col_index++ ) {
                            if( source_sets[ col_index ].Count == 0 ) values_parts[ col_index ] = Values.EMPTY_VALUES;
                            else values_parts[ col_index ] = source_sets[ col_index ][ indices[ col_index ] ];
                        }
                        associated_valueses[ current_y_index++ ] = new CombinedValues( values_parts );
                    }

                    if( indices[ inner_x ] + 1 < source_sets[ inner_x ].Count ) {
                        indices[ inner_x ]++; // increment index
                        break;
                    } else {
                        indices[ inner_x ] = 0; // clear index and continue
                        continue;
                    }
                }
            }
            return associated_valueses;
        }

        #region Add / Remove / Modify / Delete

        private void HandleAddition(int sender_index, IStreamingSet sender, ChangeArgs args) {
            
            // if this is the first addition at sender_index, all previous additions are invalid.
            if( m_MembersByInput[sender_index].RawInputValues.Count == 0 && m_ResultSet.IsEmpty == false ) {
                m_ResultSet.Clear();
            }

            foreach( Values addition in args.AdditionItems ) {
                m_MembersByInput[ sender_index ].RawInputValues.Add( addition );
            }
            CombinedValues[] resulting_valueses = GetAssociatedCombinations( sender_index, args );
            
            ChangeArgs resulting_change = new ChangeArgs( true, resulting_valueses );
            m_ResultSet.EnqueueChange( this, resulting_change );
        }

        private void HandleRemoval( int input_set_index, IStreamingSet sender, ChangeArgs args ) { throw new NotImplementedException(); }
        
        private void HandleUpdate( int input_set_index, IStreamingSet sender, ChangeArgs args ) { throw new NotImplementedException(); }

        private void HandleClear( int input_set_index, IStreamingSet sender, ChangeArgs args ) { throw new NotImplementedException(); }


        #endregion

        public event ChangeHandler RowBinding {
            add { m_ResultSet.RowBinding += value; } 
            remove { m_ResultSet.RowBinding -= value; }
        }
        
        /// <summary> The member fields which should be aligned according to the IndexOfInputStreamingSet. </summary>
        private class AlignedMembers{

            public readonly IStreamingSet InputStreamingSet;
            public readonly List<Values> RawInputValues;
            //public readonly int AssociatedIndex;
            public AlignedMembers( IStreamingSet input_streaming_set, int index, int total_set_count ) {
                //AssociatedIndex = index;
                InputStreamingSet = input_streaming_set;
                RawInputValues = new List<Values>();
            }
        }
    }

    public class BadImplementationException : Exception {
        public BadImplementationException( string incorrect_logic ) : base( incorrect_logic ) { }
    }
}
